home *** CD-ROM | disk | FTP | other *** search
/ Tech Arsenal 1 / Tech Arsenal (Arsenal Computer).ISO / tek-02 / msortp.zip / MSORTP.DOC < prev    next >
Text File  |  1993-03-02  |  25KB  |  572 lines

  1.         MSORTP - Merge Sort Unit for Protected and Real Mode
  2.                             Kim Kokkonen
  3.                          TurboPower Software
  4.                             January 1993
  5.  
  6. Introduction
  7. ---------------------------------------------------------------------
  8. MSORTP is a unit for sorting an unlimited number of items in real or
  9. protected mode applications. It can be used with TPW 1.0, TPW 1.5, or
  10. BP7 for DOS real mode, DOS protected mode, or Windows targets. Because
  11. it takes advantage of PChar strings, MSORTP cannot be used with
  12. earlier Turbo Pascal compilers.
  13.  
  14. Although MSORTP isn't source-compatible with Borland's old LSORT unit
  15. from the Database Toolbox, its interface is quite similar. Hence, it
  16. is a good candidate to replace LSORT in programs being upgraded to new
  17. platforms.
  18.  
  19. MSORTP takes advantage of all available heap space, including the
  20. extended memory heap when running in protected mode. When it runs out
  21. of heap space, it uses a merge sort algorithm that stores temporary
  22. intermediate files on disk.
  23.  
  24. MSORTP is an excerpt from TurboPower Software's commercial product
  25. B-Tree Filer.
  26.  
  27.  
  28. Using MSORTP
  29. ---------------------------------------------------------------------
  30. The following small program shows how to implement a text file sort
  31. filter using MSORTP.
  32.  
  33.   {$R-,S-,X+,I+}
  34.   program TextSort;
  35.     {-Protected mode text file sort filter}
  36.   uses
  37.     Strings, MSortP;
  38.   var
  39.     LineBuf : array[0..1024] of Char;
  40.     MaxHeap : LongInt;
  41.     MinHeap : LongInt;
  42.     Status : Word;
  43.  
  44.   procedure SendToSortEngine; far;
  45.   var
  46.     P : PChar;
  47.   begin
  48.     while not Eof do begin
  49.       {Read the next line from the input file as a PChar}
  50.       ReadLn(LineBuf);
  51.       {Don't sort empty lines to avoid StrNew quirk}
  52.       if StrLen(LineBuf) <> 0 then begin
  53.         {Copy string to heap}
  54.         P := StrNew(LineBuf);
  55.         {Store the pointer in the sort engine}
  56.         if not PutElement(P) then
  57.           Exit;
  58.       end;
  59.     end;
  60.   end;
  61.  
  62.   function Less(var X, Y) : Boolean; far;
  63.   begin
  64.     Less := (StrComp(PChar(X), PChar(Y)) < 0);
  65.   end;
  66.  
  67.   procedure GetFromSortEngine; far;
  68.   var
  69.     P : PChar;
  70.   begin
  71.     {Return each pointer and write the associated string to output}
  72.     while GetElement(P) do
  73.       WriteLn(P);
  74.   end;
  75.  
  76.   begin
  77.     {Determine how much heap space MergeSort can use}
  78.     MaxHeap := MemAvail div 10;
  79.     MinHeap := 4*MinimumHeapToUse(SizeOf(PChar));
  80.     if MaxHeap < MinHeap then
  81.       MaxHeap := MinHeap;
  82.  
  83.     {Perform the sort}
  84.     Status := MergeSort(MaxHeap, SizeOf(PChar),
  85.                         SendToSortEngine, Less, GetFromSortEngine,
  86.                         DefaultMergeName);
  87.     if Status <> 0 then
  88.       WriteLn('Sort failed, Status ', Status);
  89.   end.
  90.  
  91. This program provides three routines for the use of MSORTP.
  92. SendToSortEngine reads lines of text from the standard input device,
  93. allocates space for each line on the heap, and passes the string
  94. pointer to MSORTP by calling PutElement. Note that only the pointer is
  95. passed to MSORTP, since there is no need for MSORTP to keep a second
  96. copy of a string that is already stored in memory.
  97.  
  98. The Less function compares pairs of elements for MSORTP. This routine
  99. simply uses the StrComp function from Borland's STRINGS unit to
  100. perform a case-sensitive comparison of two PChars. Note the typecast
  101. from the untyped VAR parameters Less receives to the PChar data type
  102. stored in the sort engine.
  103.  
  104. Finally, GetFromSortEngine retrieves the elements in sorted order. In
  105. this program, GetElement return a pointer to the actual string data.
  106. When the $X+ compiler directive is enabled, as it is here, the WriteLn
  107. statement writes the string pointed to by each pointer P.
  108.  
  109. The main block of the program calls the MergeSort function to perform
  110. the sort. First it must decide how to partition the heap between the
  111. needs of string storage and the needs of MSORTP. MSORTP is given about
  112. 10% of the heap and the rest is reserved for text strings. Since the
  113. sort engine is storing 4 byte elements, this is a balanced split if
  114. the average string length is 40 bytes. Also note that the
  115. MinimumHeapToUse function is called to guarantee that the sort engine
  116. gets at least 4 times as much heap space as it requires at a minimum.
  117. In this way merging is minimized and the sort will always succeed
  118. unless there is insufficient disk space or insufficient heap space to
  119. hold the input strings.
  120.  
  121. The second parameter passed to MergeSort is the size of the elements
  122. to be sorted, in this case 4 bytes, the size of a PChar. The next
  123. three parameters are the routines used to pass elements to the sort
  124. engine, to compare elements, and to get sorted elements back from the
  125. engine. The final parameter is a routine used to name each merge file.
  126. In this program we use the default routine DefaultMergeName provided
  127. by MSORTP, which stores the merge files in the current directory.
  128.  
  129. MergeSort returns a Word result indicating the status of the sort.
  130. The result is zero to indicate success, or a non-zero result to
  131. explain the reason for a failure.
  132.  
  133. TMSORTP.PAS, also in this archive, is a more extensive program for
  134. testing the accuracy and speed of the MSORTP unit.
  135.  
  136.  
  137. Interfaced Identifiers of MSORTP
  138. ---------------------------------------------------------------------
  139. The following sections describe the constants, types, procedures, and
  140. functions interfaced by the MSORTP unit.
  141.  
  142. Constants
  143. =========
  144. MaxSelectors = 256;
  145.   Maximum number of selectors allocated. MergeSort allocates memory
  146.   buffers using segments of up to 65535 bytes, but the size is always
  147.   an even power of two times the sort record length. As a result, the
  148.   default value of MaxSelectors allows MergeSort to directly access
  149.   8-16 megabytes of extended memory. If 8 megabytes is insufficient,
  150.   you may want to increase MaxSelectors to 512. If 8 megabytes is more
  151.   than enough, you may want to decrease MaxSelectors. The data segment
  152.   space MSORTP uses for selectors (2*MaxSelectors bytes) dominates the
  153.   data segment usage of the unit.
  154.  
  155. MedianThreshold = 16;
  156.   The partition length below which the in-memory quick sort simply
  157.   uses the middle element of the partition for the pivot element. For
  158.   partition lengths at least this size, MergeSort uses the median of
  159.   the left, middle, and right elements for the pivot. The
  160.   median-of-three algorithm significantly improves the speed of large
  161.   in-memory sorts for input that is already sorted, reverse sorted, or
  162.   nearly sorted.
  163.  
  164. MergeOrder = 5;
  165.   Input files used at a time during merge. You can set MergeOrder to
  166.   any value in the range from 2 to 10 inclusive. However,
  167.   experimentation indicates that the default value of 5 is optimal
  168.   under a wide range of conditions.
  169.  
  170. MinRecsPerRun = 4;
  171.   Minimum number of records that must fit in memory during a sort. If
  172.   fewer records fit in memory, MergeInfo and MergeSort return an error
  173.   code. If even MinRecsPerRun records fit in memory, MergeSort
  174.   performs merging to complete the sort. Increase this constant if you
  175.   prefer that the sort fail instead of doing an excessive amount of
  176.   merging.
  177.  
  178. SwapThreshold = 64;
  179.   The record size below which MergeSort swaps complete data records.
  180.   For records SwapThreshold bytes or larger, MergeSort swaps pointers
  181.   to records instead of the records themselves. Swapping pointers is
  182.   the faster approach for large records sorted in memory, but this
  183.   approach has a memory overhead of 4 bytes per record plus a buffer
  184.   segment that must be used for a run output buffer. The default of 64
  185.   was chosen to keep the typical overhead below 10%. Reducing the
  186.   default also provides no significant improvement in performance.
  187.  
  188. Types
  189. =====
  190. ElementIOProc = procedure;
  191.   Specifies the type of the routine passed as the SendToSortEngine
  192.   and GetFromSortEngine parameters to MergeSort. These routines must
  193.   be declared FAR and must have no parameters.
  194.  
  195. ElementCompareFunc = function (var X, Y) : Boolean;
  196.   Specifies the type of the routine passed as the Less parameter to
  197.   MergeSort. MergeSort calls this function to compare pairs of
  198.   elements as needed. It must be declared FAR and must have the form
  199.   shown here. It should return True if and only if element X is
  200.   strictly less than element Y. You should typecast the untyped
  201.   parameters to treat them as elements of the type you are sorting.
  202.  
  203. MergeNameFunc = function (Dest : PChar; MergeNum : Word) : PChar;
  204.   Specifies the type of the routine passed as the MergeName
  205.   parameter to MergeSort. MergeSort calls this function to obtain
  206.   the name of each merge file when needed. MSORTP interfaces a
  207.   default MergeNameFunc that can be used in many circumstances. This
  208.   routine, DefaultMergeName, returns a name of the form
  209.   'SORnnnnn.TMP', where nnnnn is the merge file number given by
  210.   MergeNum. A MergeNameFunc must return a unique file name for each
  211.   value of MergeNum. Typically you specify a non-default MergeNameFunc
  212.   if you want the merge files to be located on a different drive or
  213.   directory than the current one.
  214.  
  215. MergeInfoRec =
  216.    record
  217.     SortStatus   : Word;    {Predicted status of sort, assuming disk ok}
  218.     MergeFiles   : Word;    {Total number of merge files created}
  219.     MergeHandles : Word;    {Maximum file handles used}
  220.     MergePhases  : Word;    {Number of merge phases}
  221.     MaxDiskSpace : LongInt; {Maximum peak disk space used}
  222.     HeapUsed     : LongInt; {Heap space actually used}
  223.     SelectorCount: Word;    {Number of selectors allocated}
  224.     RecsPerSel   : Word;    {Records stored in each selector}
  225.    end;
  226.   Describes the structure returned by the MergeInfo function. This
  227.   function predicts the status of a sort and its resource usage
  228.   given certain information about it. See MergeInfo for more
  229.   information.
  230.  
  231. Procedures and Functions
  232. ========================
  233. Declaration
  234.   procedure AbortSort;
  235. Purpose
  236.   Halts a sort prematurely.
  237. Description
  238.   Call this routine from your Less, SendToSortEngine, or
  239.   GetFromSortEngine routines to abort a sort. The Less function must
  240.   always return False if it calls AbortSort. When you call
  241.   AbortSort, MergeSort always returns a status of 1.
  242. See Also
  243.   MergeSort
  244.  
  245. Declaration
  246.   function DefaultMergeName(Dest : PChar; MergeNum : Word) : PChar;
  247. Purpose
  248.   Returns a default name for each merge file.
  249. Description
  250.   The default merge name is SORnnnnn.TMP, where nnnnn corresponds to
  251.   the MergeNum. For example, for MergeNum = 1, DefaultMergeName
  252.   returns 'SOR1.TMP'. For MergeNum = 999, DefaultMergeName returns
  253.   'SOR999.TMP'. For MergeNum = 65535 (the largest possible value),
  254.   DefaultMergeName returns 'SOR65535.TMP'.
  255.  
  256.   DefaultMergeName stores the null-terminated string in the buffer
  257.   pointed to by Dest, and it returns Dest as the function result.
  258.   MergeSort provides an 80 character buffer each time it calls the
  259.   merge name function.
  260.  
  261.   You can write your own merge name function that puts the merge files
  262.   somewhere besides the current directory. Follow the example of
  263.   DefaultMergeName and pass your function to MergeSort.
  264. See Also
  265.   MergeSort
  266.  
  267. Declaration
  268.   function GetElement(var X) : Boolean;
  269. Purpose
  270.   Returns the next element in sorted order.
  271. Description
  272.   Call this routine repeatedly in your GetFromSortEngine routine to
  273.   retrieve the sorted elements. GetElement returns True until there
  274.   are no more sorted elements to retrieve. GetElement copies the next
  275.   element into the variable you pass as the parameter X. Be sure that
  276.   this variable is large enough to hold an entire record; otherwise
  277.   GetElement will overwrite memory. When GetElement returns False, the
  278.   parameter X is not initialized.
  279. See Also
  280.   PutElement
  281.  
  282. Declaration
  283.   procedure MergeInfo(MaxHeapToUse : LongInt;
  284.                       RecLen : Word;
  285.                       NumRecs : LongInt;
  286.                       var MI : MergeInfoRec);
  287. Purpose
  288.   Predicts status and resource usage of a merge sort.
  289. Description
  290.   MaxHeapToUse is the maximum number of bytes of heap space the sort
  291.   should use. MergeInfo actually allocates heap space up to this
  292.   amount; if there is less heap space available, the MergeInfo results
  293.   apply only to the available heap space.
  294.  
  295.   RecLen is the size in bytes of each record to be sorted. NumRecs is
  296.   the total number of records to be sorted.
  297.  
  298.   MI returns information about the sort. MI.SortStatus is zero if the
  299.   sort is predicted to succeed. MergeInfo assumes that there is
  300.   sufficient disk space and that no disk errors will occur.
  301.  
  302.   MI.MergeFiles is the total number of merge files that will be
  303.   created.
  304.  
  305.   MI.MergeHandles is the total number of file handles used. This will
  306.   always be in the range of 0 to MergeOrder+1 inclusive.
  307.  
  308.   MI.MergePhases is the number of merge phases. A value of 0 indicates
  309.   that the sort can be done completely in memory. 1 indicates that
  310.   MergeOrder or fewer merge files are created and will be merged in
  311.   one pass. Higher values mean that multiple merge passes are
  312.   required, with the output from earlier passes feeding the input of
  313.   later passes.
  314.  
  315.   MI.MaxDiskSpace is the peak disk space required. Since merge files
  316.   are deleted as soon as they are used, the disk space used in a merge
  317.   sort grows and shrinks. All merge files are deleted when the sort is
  318.   complete. MaxDiskSpace is always smaller than 2*RecLen*NumRecs. The
  319.   analysis that MergeInfo performs to determine MaxDiskSpace requires
  320.   that MI.MergeFiles be smaller than 16384, and that 4*MI.MergeFiles
  321.   bytes of heap space be free when MergeInfo is called. If these
  322.   requirements aren't met, MergeInfo returns -1 for MI.MaxDiskSpace.
  323.  
  324.   MI.HeapUsed is the number of bytes of heap space the sort will
  325.   actually use. This is always less than or equal to MaxHeapToUse.
  326.  
  327.   MI.SelectorCount is the number of segments of heap space the sort
  328.   will allocate.
  329.  
  330.   MI.RecsPerSel is the number of records stored in each segment. This
  331.   is always a power of two.
  332. See Also
  333.   MinimumHeapToUse  OptimumHeapToUse
  334.  
  335. Declaration
  336.   function MergeSort(MaxHeapToUse : LongInt;
  337.                      RecLen : Word;
  338.                      SendToSortEngine : ElementIOProc;
  339.                      Less : ElementCompareFunc;
  340.                      GetFromSortEngine : ElementIOProc;
  341.                      MergeName : MergeNameFunc) : Word;
  342. Purpose
  343.   Sorts a group of elements.
  344. Description
  345.   MaxHeapToUse specifies the maximum number of bytes of heap space the
  346.   sort will use. It is not an error for MaxHeapToUse to exceed
  347.   MemAvail; MergeSort will use whatever is available. If you know in
  348.   advance how many records will be sorted, it is a good idea to pass
  349.   the result returned by OptimumHeapToUse for this parameter.
  350.  
  351.   RecLen is the number of bytes in each record to be sorted.
  352.  
  353.   SendToSortEngine is a procedure you write that passes the sort
  354.   elements into the sort engine. Your procedure calls PutElement for
  355.   each element to be sorted. If PutElement returns False, an error has
  356.   occurred (usually out of disk space) and you should exit your
  357.   SendToSortEngine procedure.
  358.  
  359.   Less is a function you write that compares pairs of elements. This
  360.   function must return True if and only if element "X" (the first
  361.   parameter) is strictly less than element "Y" (the second parameter).
  362.  
  363.   GetFromSortEngine is a procedure you write that retrieves the sorted
  364.   elements from the sort engine. Your procedure calls GetElement to
  365.   retrieve each element in sorted order. When GetElement returns
  366.   False, all elements have been retrieved or an error has occurred.
  367.  
  368.   MergeName is a function that provides a name for each merge file.
  369.   You can often pass DefaultMergeName for this parameter.
  370.   DefaultMergeName puts all of the merge files in the current
  371.   directory at the time of the merge sort.
  372.  
  373.   MergeSort returns a status code in its function result. It can
  374.   return the following values:
  375.  
  376.        0    success
  377.        1    user abort (AbortSort was called)
  378.        8    insufficient memory to sort
  379.       106   invalid input parameter
  380.               (RecLen zero, MaxHeapToUse too small)
  381.       204   invalid pointer returned by GlobalLock, or
  382.               SelectorInc <> 8
  383.       213   no elements available to sort
  384.       214   more than 65535 merge files
  385.      else   DOS or Turbo Pascal error code
  386.  
  387.   The most common Turbo Pascal error code returned by MergeSort is
  388.   101, which means that there was insufficient disk space to store the
  389.   merge files.
  390.  
  391.   If you want to predict whether a sort can succeed before actually
  392.   attempting it, use the MergeInfo procedure.
  393. See Also
  394.   DefaultMergeName  GetElement  MergeInfo  PutElement
  395.  
  396. Declaration
  397.   function MinimumHeapToUse(RecLen : Word) : LongInt;
  398. Purpose
  399.   Returns the minimum heap space that allows MergeSort to succeed.
  400. Description
  401.   Given the size of each record (RecLen), MinimumHeapToUse returns the
  402.   smallest amount of heap space that will allow a sort to succeed. You
  403.   can pass this value to MergeSort to sort a group of elements using
  404.   the smallest amount of memory. Note that the value returned by
  405.   MinimumHeapToUse is often very small and can cause a significant
  406.   amount of merging, so it's generally better to multiply the result
  407.   by a reasonable factor (say 2-4) even if you want to minimize heap
  408.   usage of a sort.
  409.  
  410.   Because of a quirk in the BP7 DPMI heap manager, MinimumHeapToUse
  411.   adds 2048 bytes to the actual computed heap requirement. This is
  412.   important because sometimes the DPMI heap manager consumes about
  413.   2000 bytes of heap space for its internal use.
  414. See Also
  415.   OptimumHeapToUse
  416.  
  417. Declaration
  418.   function OptimumHeapToUse(RecLen : Word;
  419.                             NumRecs : LongInt) : LongInt;
  420. Purpose
  421.   Returns the smallest heap space for a sort with no merging.
  422. Description
  423.   Given the size of each record (RecLen) and the number of records to
  424.   be sorted (NumRecs), OptimumHeapToUse returns the amount of heap
  425.   space needed to perform the sort entirely in memory. Additional heap
  426.   space does not help the sort. Less heap space causes merging.
  427.  
  428.   Because of a quirk in the BP7 DPMI heap manager, OptimumHeapToUse
  429.   adds 2048 bytes to the actual computed heap requirement. This is
  430.   important because sometimes the DPMI heap manager consumes about
  431.   2000 bytes of heap space for its internal use.
  432. See Also
  433.   MinimumHeapToUse
  434.  
  435. Declaration
  436.   function PutElement(var X) : Boolean;
  437. Purpose
  438.   Submits an element to the sort system.
  439. Description
  440.   Call this function within your SendToSortEngine routine for each
  441.   element to be sorted. Pass the element to be sorted in the untyped
  442.   parameter X. PutElement returns True if the element is successfully
  443.   processed by MergeSort. It returns False if an error occurred; do
  444.   not continue to call PutElement in this case.
  445. See Also
  446.   GetElement
  447.  
  448.  
  449. How MSORTP Works
  450. ---------------------------------------------------------------------
  451. The MergeSort function allocates a group of identical segments by
  452. calling GlobalAlloc repeatedly. (When MSORTP is compiled for DOS real
  453. mode, it provides a substitute routine for GlobalAlloc that is just a
  454. shell around the normal Turbo Pascal GetMem routine.) Each segment
  455. holds an even power-of-two number of records. The segments
  456. collectively use no more than MaxHeapToUse bytes. The last record
  457. element of the group of segments is used to store a pivot element. The
  458. next to last record element is usually used for an element swap buffer
  459. (see below). The remaining elements are collectively called the run
  460. buffer and are used for an in-memory sort of each "run" of elements.
  461. The "run" must number at least MinRecsPerRun or the sort will not
  462. proceed. MergeSort also chooses the segment size so that there are at
  463. least MergeOrder+1 segments, since these segments are reused as
  464. buffers later during the merge process.
  465.  
  466. To avoid the use of complicated code for writing and reading buffers
  467. and an excessive number of LongInt operations, each segment must be
  468. smaller than 65536 bytes. Subject to the restrictions already
  469. mentioned, however, the segment size is maximized. Hence, typical
  470. segment sizes are 32768-65535 bytes. If the segment size is 32768, the
  471. maximum memory MergeSort can access is 8MB for the default value of
  472. MaxSelectors (256). If this situation adversely effects the
  473. performance of a sort, MaxSelectors can be increased to 512 to provide
  474. access to the full 16MB of RAM that the DPMI manager can use. The cost
  475. is 512 additional bytes of data segment space. Conversely, if a sort
  476. will never need to access 8MB of memory, MaxSelectors can be reduced
  477. and data segment usage will decrease proportionately.
  478.  
  479. Elements are obtained when SendToSortEngine calls PutElement, which
  480. adds elements to the run buffer. When the run buffer fills, or when
  481. there are no more elements, the run buffer is sorted in memory using a
  482. non-recursive quicksort which calls the Less function to compare
  483. elements. If all elements fit into a single run buffer, no merging is
  484. required and GetFromSortEngine retrieves the elements directly from
  485. the sorted run buffer by calling GetElement.
  486.  
  487. If there are more elements than fit in one run buffer, the sorted run
  488. buffer is written to a merge file assigned a sequential number. Merge
  489. files are created until all elements have been obtained and all run
  490. buffers sorted.
  491.  
  492. When all merge files have been created, the merge process starts. The
  493. first MergeOrder segments of memory are reused as input buffers for
  494. reading from up to MergeOrder input files opened simultaneously. The
  495. next segment is used as an output buffer for storing the merged
  496. result. Any other allocated segments are not used during merging. Up
  497. to MergeOrder merge files are opened for input and a bufferful is read
  498. from each merge file into its buffer. The first unused element in each
  499. buffer is compared and the smallest is sent to the output. When the
  500. elements in an input file are exhausted, the file is closed and
  501. deleted. When all input files are gone, the output file is closed. The
  502. output file then becomes part of the input merge sequence for the next
  503. merge phase.
  504.  
  505. When fewer than MergeOrder input files remain, each merged output
  506. element is passed directly back to the caller via the GetElement
  507. routine instead of writing it to another merge file. In this way,
  508. MergeSort avoids a final pass of writing to disk and reading back the
  509. records.
  510.  
  511. Due to a quirk of the BP7 DPMI heap manager, MergeInfo or MergeSort
  512. may appear to leak heap space. Both routines deallocate all of the
  513. segments that they initially allocate. The quirk is that, after the
  514. DPMI heap manager makes a small number of allocations, it allocates
  515. about 2000 bytes of memory for itself and does not release this memory
  516. even if all of the user allocations are later released. Hence,
  517. MergeInfo may return a value of HeapUsed about 2000 bytes larger than
  518. needed, and MemAvail may be 2000 bytes smaller after you call
  519. MergeInfo or MergeSort. OptimumHeapToUse and MinimumHeapToUse also
  520. return a value 2048 bytes bigger than may be needed because of this
  521. quirk.
  522.  
  523. When MergeSort performs an in-memory quick sort of a run, the size of
  524. the data record determines whether MergeSort swaps pointers to records
  525. or the complete records. When record size is greater than or equal to
  526. SwapThreshold (default 64), MergeSort swaps pointers. Otherwise, it
  527. swaps entire records. When it swaps pointers, MergeSort must allocate
  528. an additional 4 bytes of space for each record to hold the swap
  529. pointer. It must also reserve one segment for writing the sorted run
  530. to a merge file. Given the default SwapThreshold value of 64, the
  531. memory overhead of swapping pointers is typically less than 10%.
  532. Swapping pointers gives large gains in sort speed (2x or more) for
  533. large records that can be sorted completely in memory.
  534.  
  535.  
  536. Version History
  537. ---------------------------------------------------------------------
  538.   Version 5.40  1/93
  539.      Initial release. Version number synchronized with that of B-Tree
  540.      Filer.
  541.  
  542. Copyright and License Information
  543. ---------------------------------------------------------------------
  544. MSORTP is copyright (c) 1993 by TurboPower Software. All Rights
  545. Reserved.
  546.  
  547. Although the MSORTP files are copyrighted, you may use and distribute
  548. them freely as long as you do not sell them or include them with other
  549. software that you sell.
  550.  
  551. The latest version of MSORTP is always available in LIB 6 of the
  552. PCVENB forum of CompuServe. It's also available on the TurboPower BBS
  553. at 719-260-9726. Look for MSORTP.ZIP.
  554.  
  555. MSORTP is an excerpt from TurboPower Software's product B-Tree Filer.
  556. B-Tree Filer is a collection of routines for managing single user and
  557. network databases, as well as for accessing common network functions
  558. on Novell and NetBIOS networks. Download FILER.BRO from LIB 6 of
  559. PCVENB for more information, or give TurboPower a call.
  560.  
  561. You can reach TurboPower at:
  562.  
  563.      TurboPower Software
  564.      P.O. Box 49009
  565.      Colorado Springs, CO 80949-9009
  566.  
  567.      719-260-6641 (voice, Monday-Friday 9AM-5PM)
  568.      719-260-7151 (fax)
  569.      719-260-9136 (sales)
  570.      719-260-9726 (BBS)
  571.      Compuserve: 76004,2611
  572.